#pragma once

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

class Shader {
 public:
  GLuint id;
 public:
  Shader();
  virtual ~Shader();
 public:
  bool loadPath(const GLchar *vertextPath, const GLchar *fragmentPath, const GLchar *geometryPath = nullptr);
  bool loadString(const GLchar *vertextString, const GLchar *fragmentString, const GLchar *geometryString = nullptr);
  bool loadBuffer(const GLchar *vertextString, const GLint vertextLength
        , const GLchar *fragmentString, const GLint fragmentLength
        , const GLchar *geometryString = nullptr, const GLint geometryLength = -1);
  inline bool isOK() { return id != 0; }
  inline void free() { if (id) { glDeleteProgram(id); id = 0; } }
 public:
  inline void use() { glUseProgram(id); }

  inline void setBool(const GLchar *name, bool value) const {
    glUniform1i(glGetUniformLocation(id, name), (int)value); }
  inline void setInt(const GLchar *name, int value) const {
    glUniform1i(glGetUniformLocation(id, name), value); }
  inline void setFloat(const GLchar *name, float value) const {
    glUniform1f(glGetUniformLocation(id, name), value); }

  inline void setVec2(const GLchar *name, float x, float y) const {
    glUniform2f(glGetUniformLocation(id, name), x, y); }
  inline void setVec2(const GLchar *name, const glm::vec2 &value) const {
    glUniform2fv(glGetUniformLocation(id, name), 1, glm::value_ptr(value)); }
  inline void setVec3(const GLchar *name, float x, float y, float z) const {
    glUniform3f(glGetUniformLocation(id, name), x, y, z); }
  inline void setVec3(const GLchar *name, const glm::vec3 &value) const {
    glUniform3fv(glGetUniformLocation(id, name), 1, glm::value_ptr(value)); }
  inline void setVec4(const GLchar *name, float x, float y, float z, float w) const {
    glUniform4f(glGetUniformLocation(id, name), x, y, z, w); }
  inline void setVec4(const GLchar *name, const glm::vec4 &value) const {
    glUniform4fv(glGetUniformLocation(id, name), 1, glm::value_ptr(value)); }

  inline void setMat2(const GLchar *name, const glm::mat2 &value) const {
    glUniformMatrix2fv(glGetUniformLocation(id, name), 1, GL_FALSE, glm::value_ptr(value)); }
  inline void setMat3(const GLchar *name, const glm::mat3 &value) const {
    glUniformMatrix3fv(glGetUniformLocation(id, name), 1, GL_FALSE, glm::value_ptr(value)); }
  inline void setMat4(const GLchar *name, const glm::mat4 &value) const {
    glUniformMatrix4fv(glGetUniformLocation(id, name), 1, GL_FALSE, glm::value_ptr(value)); }
 protected:
  static GLuint createShader(GLenum type, const GLchar *code, const GLint length = -1);
  static GLuint createProgram(GLuint vertex, GLuint fragment, GLuint geometry);
  static bool shaderHadCompileErrors(GLuint shader, GLenum type);
  static bool programHadLinkErrors(GLuint program);
};
